#pragma once

#include <rocksdb/slice.h>

#include <memory>
#include <string>

#include "redis/store/base_value_format.h"
#include "slash/coding.h"

class BaseMetaValue : public InternalValue {
 public:
  BaseMetaValue(const rocksdb::Slice& user_value) : InternalValue(user_value) {}

  size_t AppendTimestampAndVersion() override {
    size_t usize = user_value_.size();
    char* dst_ptr = start_;
    ::memcpy(dst_ptr, user_value_.data(), usize);
    dst_ptr += usize;
    slash::EncodeFixed32(dst_ptr, version_);
    dst_ptr += sizeof(int32_t);
    slash::EncodeFixed32(dst_ptr, timestamp_);
    return usize + 2 * sizeof(int32_t);
  }

  int32_t UpdateVersion() {
    int64_t unix_time;
    rocksdb::Env::Default()->GetCurrentTime(&unix_time);
    if (version_ >= static_cast<int32_t>(unix_time)) {
      version_++;
    } else {
      version_ = static_cast<int32_t>(unix_time);
    }
    return version_;
  }
};

class ParsedBaseMetaValue : public ParsedInternalValue {
 public:
  ParsedBaseMetaValue(std::string* internal_value_str)
      : ParsedInternalValue(internal_value_str) {
    if (internal_value_str->size() >= kBaseMetaValueSuffixLength) {
      user_value_ = rocksdb::Slice(
          internal_value_str->data(),
          internal_value_str->size() - kBaseMetaValueSuffixLength);
      version_ = slash::DecodeFixed32(internal_value_str->data() +
                                        internal_value_str->size() -
                                        sizeof(int32_t) * 2);
      timestamp_ =
          slash::DecodeFixed32(internal_value_str->data() +
                                 internal_value_str->size() - sizeof(int32_t));
    }
    count_ = slash::DecodeFixed32(internal_value_str->data());
  }

  ParsedBaseMetaValue(const rocksdb::Slice& internal_value_slice)
      : ParsedInternalValue(internal_value_slice) {
    if (internal_value_slice.size() >= kBaseMetaValueSuffixLength) {
      user_value_ = rocksdb::Slice(
          internal_value_slice.data(),
          internal_value_slice.size() - kBaseMetaValueSuffixLength);
      version_ = slash::DecodeFixed32(internal_value_slice.data() +
                                        internal_value_slice.size() -
                                        sizeof(int32_t) * 2);
      timestamp_ =
          slash::DecodeFixed32(internal_value_slice.data() +
                                 internal_value_slice.size() - sizeof(int32_t));
    }
    count_ = slash::DecodeFixed32(internal_value_slice.data());
  }

  void StripSuffix() override {
    if (value_ != nullptr) {
      value_->erase(value_->size() - kBaseMetaValueSuffixLength,
                    kBaseMetaValueSuffixLength);
    }
  }

  void SetVersionToValue() override {
    if (value_ != nullptr) {
      char* dst = const_cast<char*>(value_->data()) + value_->size() -
                  kBaseMetaValueSuffixLength;
      slash::EncodeFixed32(dst, version_);
    }
  }

  void SetTimeStampToValue() override {
    if (value_ != nullptr) {
      char* dst =
          const_cast<char*>(value_->data()) + value_->size() - sizeof(int32_t);
      slash::EncodeFixed32(dst, timestamp_);
    }
  }

  int32_t InitialMetaValue() {
    set_count(0);
    set_timestamp(0);
    return UpdateVersion();
  }

  int32_t count() { return count_; }

  void set_count(int32_t count) {
    count_ = count;
    if (value_ != nullptr) {
      char* dst = const_cast<char*>(value_->data());
      slash::EncodeFixed32(dst, count_);
    }
  }

  void ModifyCount(int32_t delta) {
    count_ += delta;
    if (value_ != nullptr) {
      char* dst = const_cast<char*>(value_->data());
      slash::EncodeFixed32(dst, count_);
    }
  }

  int32_t UpdateVersion() {
    int64_t unix_time;
    rocksdb::Env::Default()->GetCurrentTime(&unix_time);
    if (version_ >= static_cast<int32_t>(unix_time)) {
      version_++;
    } else {
      version_ = static_cast<int32_t>(unix_time);
    }

    SetVersionToValue();
    return version_;
  }

  static const size_t kBaseMetaValueSuffixLength = 2 * sizeof(int32_t);

 private:
  int32_t count_;
};

typedef BaseMetaValue HashesMetaValue;
typedef ParsedBaseMetaValue ParsedHashesMetaValue;
typedef BaseMetaValue SetsMetaValue;
typedef ParsedBaseMetaValue ParsedSetsMetaValue;
typedef BaseMetaValue ZSetsMetaValue;
typedef ParsedBaseMetaValue ParsedZSetsMetaValue;
